extends Node2D

# Roglike Magic Tower - single-file core logic
# - Map is always 9x9 playable cells surrounded by a 1-cell outer wall -> 11x11 total
# - Each cell is visually represented at CELL x CELL pixels (default 12x12)
# - This script generates walls, monsters, items, player start and stairs
# - Stairs may be placed on a monster or item; in that case they are hidden until
#   the monster is defeated or the item picked up. Stairs never spawn on obstacles.
# - Assets are optional; the script draws a simple colored fallback (so it runs
#   without any imported sprites). To use textures, set the texture paths below.

const CELL := 12
const INNER := 8
const OUTER := INNER + 2 # 11
const MAP_SIZE := OUTER

const MONSTER_TYPES := 3
const MONSTER_ANIM_INTERVAL := 0.45

var _monster_anim_phase := 0 # 0 or 1
var _monster_anim_timer := 0.0

enum CellKind {FLOOR, WALL, MONSTER, ITEM, STAIRS}

var map = [] # 2D array [x][y] -> dictionary describing the cell
var map_level = 0

var player = {
	"pos": Vector2(),
	"hp": 100,
	"atk": 10,
	"def": 3
}

var rng := RandomNumberGenerator.new()

@onready var _tilemap : TileMapLayer = $TileMapLayer
@onready var _player_sprite: Sprite2D = $TileMapLayer/Player

func _ready():
	rng.randomize()
	_init_map()
	# render initial map into TileMap
	call_deferred("_render_tilemap")

	# Print a small help so the user knows controls
	print("Magic Tower: use arrow keys to move. Fight monsters by moving into them. Pick items by moving onto them. Stairs appear after killing a monster or picking an item if hidden.")

func _process(delta: float) -> void:
	# advance monster animation timer; flip global phase every interval
	_monster_anim_timer += delta
	if _monster_anim_timer >= MONSTER_ANIM_INTERVAL:
		_monster_anim_timer = 0.0
		_monster_anim_phase = 1 - _monster_anim_phase
		call_deferred("_render_tilemap")

func _render_tilemap() -> void:
	if _tilemap == null:
		return
	for x in range(MAP_SIZE):
		for y in range(MAP_SIZE):
			var cell = map[x][y]
			var coords:Vector2i
			match cell["kind"]:
				CellKind.WALL:
					coords = Vector2i(8,3)
				CellKind.FLOOR:
					coords = Vector2i(22,0)
				CellKind.MONSTER:
					var mtype = 0
					if cell["variant"] and cell["variant"].has("type"):
						mtype = int(cell["variant"]["type"])
					mtype = clamp(mtype, 0, MONSTER_TYPES - 1)
					coords = Vector2i(104 + mtype, 42)
				CellKind.ITEM:
					# choose tile by item type if variant exists
					var itype = cell["variant"].type
					if itype == "atk":
						coords = Vector2i(40,6)
					elif itype == "def":
						coords = Vector2i(40,12)
					else:
						coords = Vector2i(30,4)
				CellKind.STAIRS:
					coords = Vector2i(24,0)
			if y < 9 || map_level > 1:
				_tilemap.set_cell(Vector2i(x, y), 0, coords)
	var p = player["pos"]
	_player_sprite.position = Vector2(p.x * CELL + 1, p.y * CELL + 1)

func _init_map():
	# initialize blank map
	map = []
	map_level += 1
	for x in range(MAP_SIZE):
		map.append([])
		for y in range(MAP_SIZE):
			var cell = {
				"kind": CellKind.FLOOR,
				"variant": null, # monster/item data
				"stairs_hidden": false
			}
			# outer walls
			if x == 0 or y == 0 or x == MAP_SIZE - 1 or y == MAP_SIZE - 1:
				cell["kind"] = CellKind.WALL
			map[x].append(cell)

	# Randomly place inner obstacles, monsters, and items
	# Probabilities can be tuned
	var prob_wall = 0.12
	var prob_monster = 0.10
	var prob_item = 0.04

	for x in range(1, MAP_SIZE - 1):
		for y in range(1, MAP_SIZE - 1):
			var r = rng.randf()
			if r < prob_wall:
				map[x][y]["kind"] = CellKind.WALL
			else:
				# floor by default; maybe place monster or item
				var r2 = rng.randf()
				if r2 < prob_monster:
					map[x][y]["kind"] = CellKind.MONSTER
					# simple monster stats
					var mtype = rng.randi_range(0, MONSTER_TYPES - 1)
					map[x][y]["variant"] = {
						"hp": 6 + mtype * 2,
						"atk": 2 + mtype,
						"def": clamp(mtype - 1, 0, 1),
						"type": mtype
					}
				elif r2 < prob_monster + prob_item:
					map[x][y]["kind"] = CellKind.ITEM
					var t = rng.randi_range(0, 2)
					match t:
						0:
							map[x][y]["variant"] = {"type": "atk", "value": 1}
						1:
							map[x][y]["variant"] = {"type": "def", "value": 1}
						2:
							map[x][y]["variant"] = {"type": "hp", "value": 5}

	# Place stairs: choose any non-wall cell. If the chosen cell currently has a
	# monster or item, mark the stairs as hidden until that content is cleared.
	var stairs_placed := false
	var attempts = 0
	while not stairs_placed:
		var sx = rng.randi_range(1, MAP_SIZE - 2)
		var sy = rng.randi_range(1, MAP_SIZE - 2)
		var c = map[sx][sy]
		if c["kind"] == CellKind.WALL:
			continue
		if c["kind"] == CellKind.MONSTER or c["kind"] == CellKind.ITEM:
			c["stairs_hidden"] = true
			# keep the cell kind as-is (monster/item) until cleared
		else:
			if attempts < 4:
				attempts += 1
				continue
			c["kind"] = CellKind.STAIRS
		stairs_placed = true

	# Place player start on a random floor cell (not wall, not monster/item/stairs hidden)
	var placed := false
	while not placed:
		var px = rng.randi_range(1, MAP_SIZE - 2)
		var py = rng.randi_range(1, MAP_SIZE - 2)
		var pc = map[px][py]
		if pc["kind"] == CellKind.FLOOR:
			player["pos"] = Vector2(px, py)
			placed = true

func _unhandled_input(event):
	# handle arrow keys for movement
	if event is InputEventKey and event.pressed and not event.echo:
		var dir = Vector2()
		if event.keycode == KEY_UP:
			dir = Vector2(0, -1)
		elif event.keycode == KEY_DOWN:
			dir = Vector2(0, 1)
		elif event.keycode == KEY_LEFT:
			dir = Vector2(-1, 0)
		elif event.keycode == KEY_RIGHT:
			dir = Vector2(1, 0)

		if dir != Vector2():
			_try_move(dir)

func _try_move(dir: Vector2) -> void:
	var nx = int(player["pos"].x + dir.x)
	var ny = int(player["pos"].y + dir.y)
	if nx < 0 or ny < 0 or nx >= MAP_SIZE or ny >= MAP_SIZE:
		return
	var target = map[nx][ny]
	if target["kind"] == CellKind.WALL:
		# blocked
		return
	elif target["kind"] == CellKind.MONSTER:
		_combat(nx, ny)
		return
	elif target["kind"] == CellKind.ITEM:
		_pickup_item(nx, ny)
		return
	elif target["kind"] == CellKind.STAIRS:
		# step on stairs (level up placeholder)
		player["pos"] = Vector2(nx, ny)
		_init_map()
		_render_tilemap()
	else:
		# floor or previously cleared cell
		player["pos"] = Vector2(nx, ny)

func _combat(mx: int, my: int) -> void:
	var mdata = map[mx][my]["variant"]
	if mdata == null:
		# safety: convert to floor
		map[mx][my]["kind"] = CellKind.FLOOR
		call_deferred("_render_tilemap")
		return

	var mon_hp = int(mdata["hp"])
	var mon_atk = int(mdata.get("atk", 1))
	var mon_def = int(mdata.get("def", 0))

	# Simple turn-based exchange until one side dies
	while mon_hp > 0 and player["hp"] > 0:
		# player deals damage
		var dmg = max(1, player["atk"] - mon_def)
		mon_hp -= dmg
		# monster retaliates if still alive
		if mon_hp > 0:
			var mdmg = max(1, mon_atk - player["def"])
			player["hp"] -= mdmg

	if player["hp"] <= 0:
		print("You were slain by the monster. (Game over placeholder)")
		# For now, reset HP so the demo can continue
		player["hp"] = 1
		return

	# Monster defeated
	print("Monster defeated!")
	# clear monster from map
	map[mx][my]["kind"] = CellKind.FLOOR
	map[mx][my]["variant"] = null

	# if stairs were hidden here, reveal them
	if map[mx][my]["stairs_hidden"]:
		map[mx][my]["kind"] = CellKind.STAIRS
		print("Stairs revealed!")

	# move player into the cell
	player["pos"] = Vector2(mx, my)
	call_deferred("_render_tilemap")

func _pickup_item(ix: int, iy: int) -> void:
	var ide = map[ix][iy]["variant"]
	if ide == null:
		map[ix][iy]["kind"] = CellKind.FLOOR
		call_deferred("_render_tilemap")
		return

	var t = ide.get("type")
	var v = int(ide.get("value", 0))
	match t:
		"atk":
			player["atk"] += v
			print("Picked up ATK +%d" % v)
		"def":
			player["def"] += v
			print("Picked up DEF +%d" % v)
		"hp":
			player["hp"] += v
			print("Picked up HP +%d" % v)

	# remove item
	map[ix][iy]["kind"] = CellKind.FLOOR
	map[ix][iy]["variant"] = null

	# reveal stairs if hidden here
	if map[ix][iy]["stairs_hidden"]:
		map[ix][iy]["stairs_hidden"] = false
		map[ix][iy]["kind"] = CellKind.STAIRS
		print("Stairs revealed!")

	# move player into the cell
	player["pos"] = Vector2(ix, iy)
	call_deferred("_render_tilemap")
